package com.goodcol.plugin.service.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.goodcol.plugin.dto.GenerateOptions;
import com.goodcol.plugin.dto.SettingsStorageDTO;
import com.goodcol.plugin.entity.Callback;
import com.goodcol.plugin.entity.GlobalConfigGroup;
import com.goodcol.plugin.entity.SaveFile;
import com.goodcol.plugin.entity.TableInfo;
import com.goodcol.plugin.entity.Template;
import com.goodcol.plugin.service.CodeGenerateService;
import com.goodcol.plugin.service.SettingsStorageService;
import com.goodcol.plugin.service.TableInfoSettingsService;
import com.goodcol.plugin.tool.CacheDataUtils;
import com.goodcol.plugin.tool.CloneUtils;
import com.goodcol.plugin.tool.CollectionUtil;
import com.goodcol.plugin.tool.Constants;
import com.goodcol.plugin.tool.ExtraCodeGenerateUtils;
import com.goodcol.plugin.tool.GlobalTool;
import com.goodcol.plugin.tool.ModuleUtils;
import com.goodcol.plugin.tool.NameUtils;
import com.goodcol.plugin.tool.NotificationUtils;
import com.goodcol.plugin.tool.ProjectUtils;
import com.goodcol.plugin.tool.TemplateUtils;
import com.goodcol.plugin.tool.TimeUtils;
import com.goodcol.plugin.tool.VelocityUtils;
import com.goodcol.plugin.ui.component.ListRadioComponent;
import com.intellij.database.psi.DbTable;
import com.intellij.database.util.DasUtil;
import com.intellij.database.util.DbUtil;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogBuilder;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.openapi.ui.ex.MultiLineLabel;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.ui.components.JBScrollPane;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

/**
 * @author QuickCode
 * @version 1.0.0
 */
public class CodeGenerateServiceImpl implements CodeGenerateService {
    /**
     * 项目对象
     */
    private final Project project;
    /**
     * 模型管理
     */
    private final ModuleManager moduleManager;
    /**
     * 表信息服务
     */
    private final TableInfoSettingsService tableInfoService;
    /**
     * 缓存数据工具
     */
    private final CacheDataUtils cacheDataUtils;
    /**
     * 导入包时过滤的包前缀
     */
    private static final String FILTER_PACKAGE_NAME = "java.lang";

    public CodeGenerateServiceImpl(Project project) {
        this.project = project;
        this.moduleManager = ModuleManager.getInstance(project);
        this.tableInfoService = TableInfoSettingsService.getInstance();
        this.cacheDataUtils = CacheDataUtils.getInstance();
    }

    @Override
    public void generate(List<TableInfo> tableInfoList, Collection<Template> templates,
                         GlobalConfigGroup globalConfigGroup, GenerateOptions generateOptions) {
        TableInfo firstTableInfo = tableInfoList.get(0);
        // 校验选中表的保存路径是否正确
        if (StringUtils.isEmpty(firstTableInfo.getSavePath())) {
            Messages.showInfoMessage("保存路径不能为空，请选择代码要保存的目标路径", Constants.TITLE_INFO);
            return;
        }
        // 将未配置的表进行配置覆盖
        tableInfoList.forEach(tableInfo -> {
            if (StringUtils.isEmpty(tableInfo.getSavePath())) {
                tableInfo.setSaveModelName(firstTableInfo.getSaveModelName());
                tableInfo.setSavePackageName(firstTableInfo.getSavePackageName());
                tableInfo.setSavePath(firstTableInfo.getSavePath());
                tableInfo.setPreName(firstTableInfo.getPreName());
                tableInfo.setTemplateGroupName(firstTableInfo.getTemplateGroupName());
                tableInfo.setSelectTemplateList(firstTableInfo.getSelectTemplateList());
                tableInfoService.saveTableInfo(tableInfo);
            }
        });
        // 如果使用统一配置，直接全部覆盖
        if (Boolean.TRUE.equals(generateOptions.getUnifiedConfig())) {
            tableInfoList.forEach(tableInfo -> {
                tableInfo.setSaveModelName(firstTableInfo.getSaveModelName());
                tableInfo.setSavePackageName(firstTableInfo.getSavePackageName());
                tableInfo.setSavePath(firstTableInfo.getSavePath());
                tableInfo.setPreName(firstTableInfo.getPreName());
                tableInfo.setTemplateGroupName(firstTableInfo.getTemplateGroupName());
                tableInfo.setSelectTemplateList(firstTableInfo.getSelectTemplateList());
            });
        }
        // 生成代码
        generate(templates, globalConfigGroup, tableInfoList, generateOptions, null);
    }

    /**
     * 生成代码，并自动保存到对应位置
     *
     * @param templates         模板
     * @param globalConfigGroup globalConfig组
     * @param tableInfoList     表信息对象
     * @param generateOptions   生成配置
     * @param otherParam        其他参数
     */
    public void generate(Collection<Template> templates, GlobalConfigGroup globalConfigGroup,
                         Collection<TableInfo> tableInfoList, GenerateOptions generateOptions,
                         Map<String, Object> otherParam) {
        if (CollectionUtil.isEmpty(templates) || CollectionUtil.isEmpty(tableInfoList)) {
            return;
        }
        // 处理模板，注入全局变量（克隆一份，防止篡改）
        templates = CloneUtils.cloneByJson(templates, new TypeReference<ArrayList<Template>>() {
        });
        TemplateUtils.addGlobalConfig(templates, globalConfigGroup);
        // 记录需要提示确认处理的文件列表（已经存在的文件并且没有明确选择全部覆盖或者全部不覆盖的）
        List<SaveFile> promptConfirmFileList = new ArrayList<>();
        // 生成代码
        for (TableInfo tableInfo : tableInfoList) {
            // 表名去除前缀
            String[] preArray = tableInfo.getPreName().replaceAll("\\s", "").split(",");
            Arrays.sort(preArray, (s1, s2) -> Integer.compare(s2.length(), s1.length()));
            for (String pre : preArray) {
                if (!StringUtils.isEmpty(pre.trim()) && tableInfo.getTableName().startsWith(pre.trim())) {
                    String newName = tableInfo.getTableName().substring(pre.length());
                    tableInfo.setName(NameUtils.getInstance().getClassName(newName));
                    break;
                }
            }

            // 构建参数
            Map<String, Object> param = getDefaultParam();
            // 其他参数
            if (otherParam != null) {
                param.putAll(otherParam);
            }
            // 所有表信息对象
            param.put("tableInfoList", tableInfoList);
            // 表信息对象
            param.put("tableInfo", tableInfo);
            // 设置模型路径与导包列表
            setModulePathAndImportList(param, tableInfo);
            // 设置额外代码生成服务
            param.put("generateService", new ExtraCodeGenerateUtils(this, tableInfo, generateOptions));
            for (Template template : templates) {
                Callback callback = new Callback();
                callback.setWriteFile(true);
                callback.setReformat(generateOptions.getReFormat());
                // 默认名称
                callback.setFileName(tableInfo.getName() + "Default.java");
                // 默认路径
                callback.setSavePath(tableInfo.getSavePath());
                // 设置回调对象
                param.put("callback", callback);
                // 开始生成
                String code = VelocityUtils.generate(template.getCode(), param);
                // 设置一个默认保存路径与默认文件名
                String path = callback.getSavePath();
                path = path.replace("\\", "/");
                // 针对相对路径进行处理
                if (path.startsWith(".")) {
                    path = project.getBasePath() + path.substring(1);
                }
                callback.setSavePath(path);
                SaveFile saveFile = new SaveFile(project, code, callback, generateOptions);
                VirtualFile directory = saveFile.createFileDir();
                if (directory != null) {
                    VirtualFile psiFile = directory.findChild(callback.getFileName());
                    saveFile.setFile(psiFile);
                    // 保存或覆盖
                    if (saveFile.saveOrReplaceFile() == 4) {
                        promptConfirmFileList.add(saveFile);
                    }
                }
            }
        }
        if (promptConfirmFileList.isEmpty()) {
            // 代码生成结束
            NotificationUtils.showInfoNotification(Constants.TITLE_INFO, "代码生成完成");
        } else {
            // 对已经存在的文件，并且没有明确要覆盖或者不覆盖的，弹框处理
            this.openSelectModelDialog(promptConfirmFileList);
        }
    }

    /**
     * 打开选择动作窗口，对于已经存在的文件，弹框让用户选择决定是覆盖、不覆盖还是比较
     */
    public void openSelectModelDialog(List<SaveFile> files) {
        // 创建主面板
        JPanel mainPanel = new JPanel(new VerticalFlowLayout());
        List<ListRadioComponent> radioComponentList = new ArrayList<>();
        List<String> selectItems = Arrays.asList("不覆盖文件/跳过", "覆盖文件", "比较/手动对比处理");
        for (SaveFile saveFile : files) {
            ListRadioComponent listRadioComponent = new ListRadioComponent(saveFile.getFile().getName(), selectItems, saveFile);
            radioComponentList.add(listRadioComponent);
            mainPanel.add(listRadioComponent);
        }
        // 构建dialog
        DialogBuilder dialogBuilder = new DialogBuilder(ProjectUtils.getCurrProject());
        dialogBuilder.setTitle(Constants.TITLE_INFO);
        JLabel tipLabel = new MultiLineLabel("以下文件已经存在，请选择每个文件的处理方式：");
        tipLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
        dialogBuilder.setNorthPanel(tipLabel);
        // 创建 JScrollPane 并将 contentPanel 放入其中
        JBScrollPane scrollPane = new JBScrollPane(mainPanel);
        // 设置 JScrollPane 的视口（viewport）的高度为固定的300像素
        scrollPane.setPreferredSize(new Dimension(550, 280)); // 宽度和高度都可以根据需求调整
        dialogBuilder.setCenterPanel(scrollPane);
        dialogBuilder.addActionDescriptor(dialogWrapper -> new AbstractAction("全部覆盖") {
            @Override
            public void actionPerformed(ActionEvent e) {
                radioComponentList.forEach(radioComponent -> radioComponent.getRadioList().get(1).setSelected(true));
                okActionProcess(dialogBuilder, radioComponentList, selectItems);
            }
        });
        dialogBuilder.addActionDescriptor(dialogWrapper -> new AbstractAction("全部不覆盖") {
            @Override
            public void actionPerformed(ActionEvent e) {
                radioComponentList.forEach(radioComponent -> radioComponent.getRadioList().get(0).setSelected(true));
                dialogBuilder.getDialogWrapper().close(DialogWrapper.CANCEL_EXIT_CODE);
            }
        });
        dialogBuilder.addActionDescriptor(dialogWrapper -> new AbstractAction("全部比较") {
            @Override
            public void actionPerformed(ActionEvent e) {
                radioComponentList.forEach(radioComponent -> radioComponent.getRadioList().get(2).setSelected(true));
                okActionProcess(dialogBuilder, radioComponentList, selectItems);
            }
        });
        dialogBuilder.addOkAction();
        dialogBuilder.setOkOperation(() -> okActionProcess(dialogBuilder, radioComponentList, selectItems));
        dialogBuilder.addCancelAction();
        // 显示窗口
        dialogBuilder.show();
    }

    /**
     * 点击OK按钮的处理
     *
     * @param dialogBuilder      dialogBuilder
     * @param radioComponentList radioComponentList
     * @param selectItems        selectItems
     */
    private void okActionProcess(DialogBuilder dialogBuilder, List<ListRadioComponent> radioComponentList, List<String> selectItems) {
        // 读取每个文件选中的选项做对应处理
        //1.需要覆盖的做覆盖处理
        //2.不需要覆盖的不做任何处理直接跳过
        //3.需要比较的，循环弹出diff对话框
        radioComponentList.forEach(listRadio -> {
            String selectItem = listRadio.getSelectedItem();
            SaveFile sfile = (SaveFile) listRadio.getExtData();
            if (selectItems.get(1).equals(selectItem)) { // 覆盖
                sfile.convertFileHandler(1);
            } else if (selectItems.get(2).equals(selectItem)) { // 比较
                sfile.convertFileHandler(2);
            }
        });
        dialogBuilder.getDialogWrapper().close(DialogWrapper.OK_EXIT_CODE);
        NotificationUtils.showInfoNotification(Constants.TITLE_INFO, "代码生成完成");
    }

    /**
     * 生成代码
     *
     * @param template          模板
     * @param globalConfigGroup globalConfigGroup
     * @param tableInfo         表信息对象
     * @return 生成好的代码
     */
    @Override
    public String generate(Template template, GlobalConfigGroup globalConfigGroup, TableInfo tableInfo) {
        // 获取默认参数
        Map<String, Object> param = getDefaultParam();
        // 表信息对象，进行克隆，防止篡改
        param.put("tableInfo", tableInfo);
        // 设置模型路径与导包列表
        setModulePathAndImportList(param, tableInfo);
        // 处理模板，注入全局变量
        TemplateUtils.addGlobalConfig(template, globalConfigGroup);
        return VelocityUtils.generate(template.getCode(), param);
    }

    /**
     * 设置模型路径与导包列表
     *
     * @param param     参数
     * @param tableInfo 表信息对象
     */
    private void setModulePathAndImportList(Map<String, Object> param, TableInfo tableInfo) {
        Module module = null;
        if (!StringUtils.isEmpty(tableInfo.getSaveModelName())) {
            module = this.moduleManager.findModuleByName(tableInfo.getSaveModelName());
        }
        if (module != null) {
            // 设置modulePath
            param.put("modulePath", ModuleUtils.getModuleDir(module).getPath());
        }
        // 设置要导入的包
        param.put("importList", getImportList(tableInfo));
    }

    /**
     * 获取默认参数
     *
     * @return 参数
     */
    private Map<String, Object> getDefaultParam() {
        // 系统设置
        SettingsStorageDTO settings = SettingsStorageService.getSettingsStorage();
        Map<String, Object> param = new HashMap<>(20);
        // 作者
        param.put("author", settings.getAuthor());
        //工具类
        param.put("tool", GlobalTool.getInstance());
//        param.put("stringUtils", ReflectionUtil.newInstance(StringUtils.class));
        param.put("stringUtils", StringUtils.class);
        param.put("time", TimeUtils.getInstance());
        // 项目路径
        param.put("projectPath", project.getBasePath());
        // Database数据库工具
//        param.put("dbUtil", ReflectionUtil.newInstance(DbUtil.class));
        param.put("dbUtil", DbUtil.class);
//        param.put("dasUtil", ReflectionUtil.newInstance(DasUtil.class));
        param.put("dasUtil", DasUtil.class);
        return param;
    }

    /**
     * 获取导入列表
     *
     * @param tableInfo 表信息对象
     * @return 导入列表
     */
    private Set<String> getImportList(TableInfo tableInfo) {
        // 创建一个自带排序的集合
        Set<String> result = new TreeSet<>();
        tableInfo.getFullColumn().forEach(columnInfo -> {
            if (!columnInfo.getType().startsWith(FILTER_PACKAGE_NAME)) {
                String type = NameUtils.getInstance().getClsFullNameRemoveGeneric(columnInfo.getType());
                result.add(type);
            }
        });
        return result;
    }

    @Override
    public boolean cacheSelectedTables(@NotNull AnActionEvent event) {
        //获取选中的所有表
        PsiElement[] psiElements = event.getData(PlatformDataKeys.PSI_ELEMENT_ARRAY);
        if (psiElements == null || psiElements.length == 0) {
            return false;
        }
        List<DbTable> dbTableList = Arrays.stream(psiElements)
                .filter(ele -> ele instanceof DbTable)
                .map(ele -> (DbTable) ele).collect(Collectors.toList());
        if (dbTableList.isEmpty()) {
            return false;
        }

        //保存数据到缓存
        cacheDataUtils.setDbTableList(dbTableList);
        cacheDataUtils.setSelectDbTable(dbTableList.get(0));
        return true;
    }

}
